/*!	 synfig/rendering/software/function/blur.cpp
**	 Blur
**
**	......... ... 2015 Ivan Mahonin
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#ifndef _WIN32
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#endif

#include <cassert>

#include <algorithm>
#include <functional>

#include "blur.h"

#include "blurtemplates.h"
#include "fft.h"
#include <synfig/angle.h>
#include <synfig/general.h>

#endif

using namespace std;
using namespace synfig;
using namespace rendering;

bool
software::Blur::Params::validate()
{
    const Real precision = 1e-10;

    if (blend && fabs(amount) < precision) {
        return false;
    }

    if (!dest->is_valid()
            || !dest_rect.valid()
            || !src->is_valid()) {
        return false;
    }

    amplified_size = size * get_size_amplifier(type);
    amplified_size[0] = fabs(amplified_size[0]);
    amplified_size[1] = fabs(amplified_size[1]);

    extra_size = get_extra_size(type, size);
    etl::set_intersect(dest_rect, dest_rect, RectInt(0, 0, dest->get_w(), dest->get_h()));

    if (!dest_rect.valid()) {
        return false;
    }

    src_rect = dest_rect - dest_rect.get_min() + src_offset;
    src_rect.minx -= extra_size[0];
    src_rect.miny -= extra_size[1];
    src_rect.maxx += extra_size[0];
    src_rect.maxy += extra_size[1];

    if (!src_rect.valid()) {
        return false;
    }

    etl::set_intersect(src_rect, src_rect, RectInt(0, 0, src->get_w(), src->get_h()));

    if (!src_rect.valid()) {
        return false;
    }

    etl::set_intersect(dest_rect, dest_rect, src_rect - src_offset + dest_rect.get_min());

    if (!dest_rect.valid()) {
        return false;
    }

    offset = src_offset - src_rect.get_min();

    return true;
}

Real
software::Blur::get_size_amplifier(rendering::Blur::Type type)
{
    switch (type) {
    case rendering::Blur::BOX:
        return BlurTemplates::amplifier_box<Real>();

    case rendering::Blur::CROSS:
        return BlurTemplates::amplifier_cross<Real>();

    case rendering::Blur::DISC:
        return BlurTemplates::amplifier_disk<Real>();

    case rendering::Blur::FASTGAUSSIAN:
        return BlurTemplates::amplifier_fastgauss<Real>();

    case rendering::Blur::GAUSSIAN:
        return BlurTemplates::amplifier_gauss<Real>();

    default:
        break;
    }

    return 0.0;
}

Real
software::Blur::get_extra_size(rendering::Blur::Type type)
{
    static const Real min_value = 1.0 / 4096.0;
    static const Real gauss_size = BlurTemplates::ungauss(min_value)
                                   * BlurTemplates::amplifier_gauss<Real>();

    static const Real fast_min_value = 1.0 / 128.0;
    static const Real fast_gauss_size = BlurTemplates::ungauss(fast_min_value)
                                        * BlurTemplates::amplifier_gauss<Real>();

    switch (type) {
    case rendering::Blur::BOX:
        return BlurTemplates::amplifier_box<Real>();

    case rendering::Blur::CROSS:
        return BlurTemplates::amplifier_cross<Real>();

    case rendering::Blur::DISC:
        return BlurTemplates::amplifier_disk<Real>();

    case rendering::Blur::FASTGAUSSIAN:
        return fast_gauss_size;

    case rendering::Blur::GAUSSIAN:
        return gauss_size;

    default:
        break;
    }

    return 0.0;
}

VectorInt
software::Blur::get_extra_size(
    rendering::Blur::Type type,
    const Vector &size)
{
    const Real precision = 1e-10;
    Vector s = size * get_extra_size(type);
    return VectorInt((int)ceil(fabs(s[0]) + 1.0 - precision), (int)ceil(fabs(s[1]) + 1.0 - precision));
}

void
software::Blur::blur_pattern(const Params &params)
{
    // init
    const int channels = 4;
    int rows = params.src_rect.get_size()[1];
    int cols = params.src_rect.get_size()[0];
    int pattern_rows = params.extra_size[1] + 1;
    int pattern_cols = params.extra_size[0] + 1;
    vector<ColorReal> src_surface(rows * cols * channels);
    vector<ColorReal> dst_surface(rows * cols * channels);
    vector<ColorReal> full_pattern;
    vector<ColorReal> row_pattern;
    vector<ColorReal> col_pattern;
    bool full = false;
    bool cross = false;

    Array<ColorReal, 3> arr_src_surface(&src_surface.front());
    Array<ColorReal, 3> arr_dst_surface(&dst_surface.front());

    arr_src_surface
    .set_dim(rows, cols * channels)
    .set_dim(cols, channels)
    .set_dim(channels, 1);
    arr_dst_surface
    .set_dim(rows, cols * channels)
    .set_dim(cols, channels)
    .set_dim(channels, 1);
    Array<ColorReal, 2> arr_full_pattern;
    arr_full_pattern
    .set_dim(pattern_rows, pattern_cols)
    .set_dim(pattern_cols, 1);
    Array<ColorReal, 1> arr_row_pattern;
    arr_row_pattern
    .set_dim(pattern_cols, 1);
    Array<ColorReal, 1> arr_col_pattern;
    arr_col_pattern
    .set_dim(pattern_rows, 1);

    // prepare surface (apply alpha)
    BlurTemplates::surface_read(arr_src_surface, *params.src, VectorInt(0, 0), params.src_rect);

    // alloc memory
    switch (params.type) {
    case rendering::Blur::BOX:
    case rendering::Blur::CROSS:
    case rendering::Blur::GAUSSIAN:
    case rendering::Blur::FASTGAUSSIAN:
        row_pattern.resize(pattern_cols);
        col_pattern.resize(pattern_rows);
        arr_row_pattern.pointer = &row_pattern.front();
        arr_col_pattern.pointer = &col_pattern.front();
        break;

    case rendering::Blur::DISC:
        full_pattern.resize(pattern_rows * pattern_cols);
        arr_full_pattern.pointer = &full_pattern.front();
        break;

    default:
        assert(false);
        return;
    }

    // create patterns
    switch (params.type) {
    case rendering::Blur::BOX:
        BlurTemplates::fill_pattern_box(arr_row_pattern, (ColorReal)params.amplified_size[0]);
        BlurTemplates::fill_pattern_box(arr_col_pattern, (ColorReal)params.amplified_size[1]);
        break;

    case rendering::Blur::CROSS:
        BlurTemplates::fill_pattern_box(arr_row_pattern, (ColorReal)params.amplified_size[0]);
        BlurTemplates::fill_pattern_box(arr_col_pattern, (ColorReal)params.amplified_size[1]);
        cross = true;
        break;

    case rendering::Blur::GAUSSIAN:
    case rendering::Blur::FASTGAUSSIAN:
        BlurTemplates::fill_pattern_gauss(arr_row_pattern, (ColorReal)params.amplified_size[0]);
        BlurTemplates::fill_pattern_gauss(arr_col_pattern, (ColorReal)params.amplified_size[1]);
        break;

    case rendering::Blur::DISC:
        BlurTemplates::fill_pattern_2d_disk(
            arr_full_pattern,
            (ColorReal)params.amplified_size[0],
            (ColorReal)params.amplified_size[1]);
        full = true;
        break;

    default:
        assert(false);
        return;
    }

    // process
    if (full) {
        BlurTemplates::normalize_half_pattern_2d(arr_full_pattern);

        for (Array<ColorReal, 3>::Iterator dst(arr_dst_surface.reorder(2, 0, 1)), src(arr_src_surface.reorder(2, 0, 1)); dst; ++dst, ++src) {
            BlurTemplates::blur_2d_pattern(*dst, *src, arr_full_pattern);
        }
    } else {
        BlurTemplates::normalize_half_pattern(arr_row_pattern);
        BlurTemplates::normalize_half_pattern(arr_col_pattern);

        Array<ColorReal, 3> arr_src_surface_rows(arr_src_surface.reorder(2, 0, 1));
        Array<ColorReal, 3> arr_src_surface_cols(arr_src_surface_rows.reorder(0, 2, 1));

        Array<ColorReal, 3> arr_dst_surface_rows(arr_dst_surface.reorder(2, 0, 1));
        Array<ColorReal, 3> arr_dst_surface_cols(arr_dst_surface_rows.reorder(0, 2, 1));

        if (cross) {
            arr_row_pattern.process< std::multiplies<ColorReal> >(0.5);
            arr_col_pattern.process< std::multiplies<ColorReal> >(0.5);
        }

        for (Array<ColorReal, 3>::Iterator src_channel(arr_src_surface_rows), dst_channel(arr_dst_surface_rows); dst_channel; ++src_channel, ++dst_channel)
            for (Array<ColorReal, 2>::Iterator sr(*src_channel), dr(*dst_channel); dr; ++sr, ++dr) {
                BlurTemplates::blur_pattern(*dr, *sr, arr_row_pattern);
            }

        if (!cross) {
            swap(arr_src_surface_cols.pointer, arr_dst_surface_cols.pointer);
            swap(arr_src_surface.pointer, arr_dst_surface.pointer);
            memset(&src_surface.front(), 0, sizeof(src_surface.front())*src_surface.size());
        }

        for (Array<ColorReal, 3>::Iterator src_channel(arr_src_surface_cols), dst_channel(arr_dst_surface_cols); dst_channel; ++src_channel, ++dst_channel)
            for (Array<ColorReal, 2>::Iterator sr(*src_channel), dr(*dst_channel); dr; ++sr, ++dr) {
                BlurTemplates::blur_pattern(*dr, *sr, arr_row_pattern);
            }
    }

    // copy result surface and restore alpha
    BlurTemplates::surface_write(
        *params.dest,
        arr_dst_surface,
        params.dest_rect,
        params.offset,
        params.blend,
        params.blend_method,
        params.amount);
}

void
software::Blur::blur_fft(const Params &params)
{
    // init
    const int channels = 4;
    int rows = FFT::get_valid_count(params.src_rect.get_size()[1]);
    int cols = FFT::get_valid_count(params.src_rect.get_size()[0]);
    vector<Complex> surface(rows * cols * channels);
    vector<Complex> full_pattern;
    vector<Complex> row_pattern;
    vector<Complex> col_pattern;
    bool full = false;
    bool cross = false;

    Array<Real, 4> arr_surface((Real*)&surface.front());

    arr_surface
    .set_dim(rows, cols * channels * 2)
    .set_dim(cols, channels * 2)
    .set_dim(channels, 2)
    .set_dim(2, 1);
    Array<Real, 3> arr_full_pattern;
    arr_full_pattern
    .set_dim(rows, 2 * cols)
    .set_dim(cols, 2)
    .set_dim(2, 1);
    Array<Real, 2> arr_row_pattern;
    arr_row_pattern
    .set_dim(cols, 2)
    .set_dim(2, 1);
    Array<Real, 2> arr_col_pattern;
    arr_col_pattern
    .set_dim(rows, 2)
    .set_dim(2, 1);

    // convert surface to complex
    BlurTemplates::surface_read(arr_surface.reorder(0, 1, 2), *params.src, VectorInt(0, 0), params.src_rect);

    // alloc memory
    switch (params.type) {
    case rendering::Blur::BOX:
    case rendering::Blur::CROSS:
    case rendering::Blur::GAUSSIAN:
    case rendering::Blur::FASTGAUSSIAN:
        row_pattern.resize(cols);
        col_pattern.resize(rows);
        arr_row_pattern.pointer = (Real*)&row_pattern.front();
        arr_col_pattern.pointer = (Real*)&col_pattern.front();
        break;

    case rendering::Blur::DISC:
        full_pattern.resize(rows * cols);
        arr_full_pattern.pointer = (Real*)&full_pattern.front();
        break;

    default:
        assert(false);
        return;
    }

    // create patterns
    switch (params.type) {
    case rendering::Blur::BOX:
        BlurTemplates::fill_pattern_box(arr_row_pattern.reorder(0), params.amplified_size[0]);
        BlurTemplates::fill_pattern_box(arr_col_pattern.reorder(0), params.amplified_size[1]);
        break;

    case rendering::Blur::CROSS:
        BlurTemplates::fill_pattern_box(arr_row_pattern.reorder(0), params.amplified_size[0]);
        BlurTemplates::fill_pattern_box(arr_col_pattern.reorder(0), params.amplified_size[1]);
        cross = true;
        break;

    case rendering::Blur::GAUSSIAN:
    case rendering::Blur::FASTGAUSSIAN:
        BlurTemplates::fill_pattern_gauss(arr_row_pattern.reorder(0), params.amplified_size[0]);
        BlurTemplates::fill_pattern_gauss(arr_col_pattern.reorder(0), params.amplified_size[1]);
        break;

    case rendering::Blur::DISC:
        BlurTemplates::fill_pattern_2d_disk(
            arr_full_pattern.reorder(0, 1),
            params.amplified_size[0],
            params.amplified_size[1]);
        full = true;
        break;

    default:
        assert(false);
        return;
    }

    // process
    if (full) {
        BlurTemplates::mirror_pattern_2d(arr_full_pattern.reorder(0, 1));
        BlurTemplates::normalize_full_pattern_2d(arr_full_pattern.reorder(0, 1));

        FFT::fft2d(arr_full_pattern.group_items<Complex>(), false);

        for (Array<Complex, 3>::Iterator channel(arr_surface.group_items<Complex>().reorder(2, 0, 1)); channel; ++channel) {
            FFT::fft2d(*channel, false);
            channel->process< std::multiplies<Complex> >(arr_full_pattern.group_items<Complex>());
            FFT::fft2d(*channel, true);
        }
    } else {
        BlurTemplates::mirror_pattern(arr_row_pattern.reorder(0));
        BlurTemplates::mirror_pattern(arr_col_pattern.reorder(0));
        BlurTemplates::normalize_full_pattern(arr_row_pattern.reorder(0));
        BlurTemplates::normalize_full_pattern(arr_col_pattern.reorder(0));

        vector<Complex> surface_copy;
        Array<Complex, 3> arr_surface_rows(arr_surface.group_items<Complex>().reorder(2, 0, 1));
        Array<Complex, 3> arr_surface_cols(arr_surface_rows.reorder(0, 2, 1));

        if (cross) {
            arr_row_pattern.reorder(0).process< std::multiplies<Real> >(0.5);
            arr_col_pattern.reorder(0).process< std::multiplies<Real> >(0.5);
            surface_copy = surface;
            arr_surface_cols.pointer = &surface_copy.front();
        }

        FFT::fft(arr_row_pattern.group_items<Complex>(), false);

        for (Array<Complex, 3>::Iterator channel(arr_surface_rows); channel; ++channel) {
            FFT::fft2d(*channel, false, true, false);

            for (Array<Complex, 2>::Iterator r(*channel); r; ++r) {
                r->process< std::multiplies<Complex> >(arr_row_pattern.group_items<Complex>());
            }

            FFT::fft2d(*channel, true, true, false);
        }

        FFT::fft(arr_col_pattern.group_items<Complex>(), false);

        for (Array<Complex, 3>::Iterator channel(arr_surface_cols); channel; ++channel) {
            FFT::fft2d(*channel, false, true, false);

            for (Array<Complex, 2>::Iterator c(*channel); c; ++c) {
                c->process< std::multiplies<Complex> >(arr_col_pattern.group_items<Complex>());
            }

            FFT::fft2d(*channel, true, true, false);
        }

        arr_surface_rows.process< BlurTemplates::Abs<Complex> >();

        if (cross) {
            arr_surface_cols.process< BlurTemplates::Abs<Complex> >();
            arr_surface_rows.split_items<Real>().reorder(0, 1, 2)
            .process< std::plus<Real> >(
                arr_surface_cols.split_items<Real>().reorder(0, 2, 1));
        }
    }

    // convert surface from complex to color
    BlurTemplates::surface_write(
        *params.dest,
        arr_surface.reorder(0, 1, 2),
        params.dest_rect,
        params.offset,
        params.blend,
        params.blend_method,
        params.amount);
}

void
software::Blur::blur_box(const Params &params)
{
    const Real precision(1e-5);
    const int channels = 4;
    int rows = params.src_rect.get_size()[1];
    int cols = params.src_rect.get_size()[0];

    vector<ColorReal> surface(rows * cols * channels);
    Array<ColorReal, 3> arr_surface(&surface.front());
    arr_surface
    .set_dim(rows, cols * channels)
    .set_dim(cols, channels)
    .set_dim(channels, 1);
    BlurTemplates::surface_read(arr_surface, *params.src, VectorInt(0, 0), params.src_rect);

    Vector size = params.amplified_size;
    bool cross = false;
    bool count = 1;

    switch (params.type) {
    case rendering::Blur::BOX:
        break;

    case rendering::Blur::GAUSSIAN:
    case rendering::Blur::FASTGAUSSIAN:
        size *= 1.662155813;
        count = 2;
        break;

    case rendering::Blur::CROSS:
        cross = true;
        break;

    default:
        assert(false);
        return;
    }

    deque<ColorReal> q;
    vector<ColorReal> surface_copy;
    Array<ColorReal, 3> arr_surface_rows(arr_surface.reorder(2, 0, 1));
    Array<ColorReal, 3> arr_surface_cols(arr_surface_rows.reorder(0, 2, 1));

    if (cross) {
        arr_surface.process< std::multiplies<ColorReal> >(0.5);
        surface_copy = surface;
        arr_surface_cols.pointer = &surface_copy.front();
    }

    if (true || fabs(size[0] - round(size[0])) < precision)
        for (Array<ColorReal, 3>::Iterator channel(arr_surface_rows); channel; ++channel)
            for (Array<ColorReal, 2>::Iterator r(*channel); r; ++r)
                for (int i = 0; i < count; ++i) {
                    BlurTemplates::blur_box_discrete(*r, q, (int)round(size[0]));
                } else
        for (Array<ColorReal, 3>::Iterator channel(arr_surface_rows); channel; ++channel)
            for (Array<ColorReal, 2>::Iterator r(*channel); r; ++r)
                for (int i = 0; i < count; ++i) {
                    BlurTemplates::blur_box_aa(*r, q, (ColorReal)size[0]);
                }

    if (true || fabs(size[1] - round(size[1])) < precision)
        for (Array<ColorReal, 3>::Iterator channel(arr_surface_cols); channel; ++channel)
            for (Array<ColorReal, 2>::Iterator c(*channel); c; ++c)
                for (int i = 0; i < count; ++i) {
                    BlurTemplates::blur_box_discrete(*c, q, (int)round(size[1]));
                } else
        for (Array<ColorReal, 3>::Iterator channel(arr_surface_cols); channel; ++channel)
            for (Array<ColorReal, 2>::Iterator c(*channel); c; ++c)
                for (int i = 0; i < count; ++i) {
                    BlurTemplates::blur_box_aa(*c, q, (ColorReal)size[1]);
                }

    if (cross)
        arr_surface_rows
        .process< std::plus<ColorReal> >(
            arr_surface_cols.reorder(0, 2, 1));

    BlurTemplates::surface_write(
        *params.dest,
        arr_surface,
        params.dest_rect,
        params.offset,
        params.blend,
        params.blend_method,
        params.amount);
}

software::Blur::IIRCoefficients
software::Blur::get_iir_coefficients(Real radius)
{
    const Real precision(1e-8);
    radius = max(iir_min_radius + precision, min(iir_max_radius - precision, fabs(radius)));

    Real x = (radius - iir_min_radius) / iir_radius_step;
    // x -= index;
    int index = round(x);
    x = 0.0;

    const Real *prev = iir_coefficients_unprepared[index];
    const Real *next = iir_coefficients_unprepared[index + 1];

    Real k[3];

    for (int i = 0; i < 3; ++i) {
        k[i] = prev[i] * (1.0 - x) + next[i] * x;
    }

    Real a = 1.0 / k[0];
    Real b = a * cos(PI * k[1]);
    Real c = 1.0 / k[2];

    IIRCoefficients coef;
    coef.k1 = (a * a + 2.0 * c * b) / (c * a * a);
    coef.k2 = -(c + 2.0 * b) / (c * a * a);
    coef.k3 = 1.0 / (c * a * a);

    coef.k0 = 1.0 - coef.k1 - coef.k2 - coef.k3;

    return coef;
}

void
software::Blur::blur_iir(const Params &params)
{
    const Real precision(1e-5);
    const int channels = 4;
    int rows = params.src_rect.get_size()[1];
    int cols = params.src_rect.get_size()[0];
    int pattern_rows = params.extra_size[1] + 1;
    int pattern_cols = params.extra_size[0] + 1;

    vector<ColorReal> surface(rows * cols * channels);
    vector<ColorReal> tmp_surface;
    vector<ColorReal> row_pattern;
    vector<ColorReal> col_pattern;

    Array<ColorReal, 3> arr_surface(&surface.front());
    arr_surface
    .set_dim(rows, cols * channels)
    .set_dim(cols, channels)
    .set_dim(channels, 1);
    Array<ColorReal, 1> arr_row_pattern;
    arr_row_pattern
    .set_dim(pattern_cols, 1);
    Array<ColorReal, 1> arr_col_pattern;
    arr_col_pattern
    .set_dim(pattern_rows, 1);
    bool use_row_pattern = (params.extra_size[0] < 4 && params.amplified_size[0] < 2.0);
    bool use_col_pattern = (params.extra_size[1] < 4 && params.amplified_size[1] < 2.0);
    bool use_pattern = use_row_pattern || use_col_pattern;

    if (use_pattern) {
        tmp_surface.resize(rows * cols * channels);
    }

    Array<ColorReal, 3> arr_tmp_surface(
        use_pattern ? &tmp_surface.front() : &surface.front(),
        arr_surface);


    BlurTemplates::surface_read(arr_surface, *params.src, VectorInt(0, 0), params.src_rect);

    switch (params.type) {
    case rendering::Blur::GAUSSIAN:
    case rendering::Blur::FASTGAUSSIAN:
        if (use_row_pattern) {
            row_pattern.resize(pattern_cols);
            arr_row_pattern.pointer = &row_pattern.front();
            BlurTemplates::fill_pattern_gauss(arr_row_pattern, (ColorReal)params.amplified_size[0]);
            BlurTemplates::normalize_half_pattern(arr_row_pattern.reorder(0));
        }

        if (use_col_pattern) {
            col_pattern.resize(pattern_rows);
            arr_col_pattern.pointer = &col_pattern.front();
            BlurTemplates::fill_pattern_gauss(arr_col_pattern, (ColorReal)params.amplified_size[1]);
            BlurTemplates::normalize_half_pattern(arr_col_pattern.reorder(0));
        }

        break;

    default:
        assert(false);
        return;
    }

    Array<ColorReal, 3> arr_surface_rows(arr_surface.reorder(2, 0, 1));
    Array<ColorReal, 3> arr_surface_cols(arr_surface_rows.reorder(0, 2, 1));

    IIRCoefficients cr = get_iir_coefficients(params.amplified_size[0]);
    IIRCoefficients cc = get_iir_coefficients(params.amplified_size[1]);

    ColorReal cr0 = (ColorReal)cr.k0;
    ColorReal cr1 = (ColorReal)cr.k1;
    ColorReal cr2 = (ColorReal)cr.k2;
    ColorReal cr3 = (ColorReal)cr.k3;

    ColorReal cc0 = (ColorReal)cc.k0;
    ColorReal cc1 = (ColorReal)cc.k1;
    ColorReal cc2 = (ColorReal)cc.k2;
    ColorReal cc3 = (ColorReal)cc.k3;

    Array<ColorReal, 3> arr_src_surface_rows(arr_surface.reorder(2, 0, 1));
    Array<ColorReal, 3> arr_src_surface_cols(arr_surface_rows.reorder(0, 2, 1));

    Array<ColorReal, 3> arr_dst_surface_rows(arr_tmp_surface.reorder(2, 0, 1));
    Array<ColorReal, 3> arr_dst_surface_cols(arr_dst_surface_rows.reorder(0, 2, 1));

    if (fabs(params.amplified_size[0]) > precision) {
        if (use_row_pattern) {
            for (Array<ColorReal, 3>::Iterator src_channel(arr_src_surface_rows), dst_channel(arr_dst_surface_rows); dst_channel; ++src_channel, ++dst_channel)
                for (Array<ColorReal, 2>::Iterator sr(*src_channel), dr(*dst_channel); dr; ++sr, ++dr) {
                    BlurTemplates::blur_pattern(*dr, *sr, arr_row_pattern);
                }

            swap(arr_src_surface_cols.pointer, arr_dst_surface_cols.pointer);
            swap(arr_surface.pointer, arr_tmp_surface.pointer);
            arr_surface_cols.pointer = arr_surface.pointer;
            memset(&surface.front(), 0, sizeof(surface.front())*surface.size());
        } else {
            for (Array<ColorReal, 3>::Iterator channel(arr_surface_rows); channel; ++channel)
                for (Array<ColorReal, 2>::Iterator r(*channel); r; ++r) {
                    BlurTemplates::blur_iir(*r, cr0, cr1, cr2, cr3);
                }
        }
    }

    if (fabs(params.amplified_size[1]) > precision) {
        if (use_col_pattern) {
            for (Array<ColorReal, 3>::Iterator src_channel(arr_src_surface_cols), dst_channel(arr_dst_surface_cols); dst_channel; ++src_channel, ++dst_channel)
                for (Array<ColorReal, 2>::Iterator sr(*src_channel), dr(*dst_channel); dr; ++sr, ++dr) {
                    BlurTemplates::blur_pattern(*dr, *sr, arr_col_pattern);
                }

            swap(arr_surface.pointer, arr_tmp_surface.pointer);
        } else {
            for (Array<ColorReal, 3>::Iterator channel(arr_surface_cols); channel; ++channel)
                for (Array<ColorReal, 2>::Iterator c(*channel); c; ++c) {
                    BlurTemplates::blur_iir(*c, cc0, cc1, cc2, cc3);
                }
        }
    }

    BlurTemplates::surface_write(
        *params.dest,
        arr_surface,
        params.dest_rect,
        params.offset,
        params.blend,
        params.blend_method,
        params.amount);
}

void
software::Blur::blur(Params params)
{
    if (!params.validate()) {
        return;
    }

    if (params.type == rendering::Blur::BOX
            || params.type == rendering::Blur::CROSS) {
        blur_box(params);
        return;
    }

    if (params.type == rendering::Blur::DISC
            && fabs(params.extra_size[0]) < 8
            && fabs(params.extra_size[1]) < 8) {
        blur_pattern(params);
        return;
    }

    if (params.type == rendering::Blur::DISC
            && (params.extra_size[0] + 1) * (params.extra_size[1] + 1) < 64.0) {
        blur_pattern(params);
        return;
    }

    if (params.type == rendering::Blur::GAUSSIAN
            && params.extra_size[0] < 32
            && params.extra_size[1] < 32) {
        blur_pattern(params);
        return;
    }

    if (params.type == rendering::Blur::FASTGAUSSIAN) {
        blur_box(params);
        return;
    }

    blur_fft(params);
}